package com.fm.webrtc.websocket.controller;

import android.content.Context;
import android.util.Log;

import com.fm.mediasoup.Producer;
import com.fm.mediasoup.callback.TrackCallback;
import com.fm.mediasoup.sdpTransform.Sdp;
import com.fm.mediasoup.sdpTransform.SdpTransform;
import com.fm.mediasoup.callback.CallbackMediaStream;
import com.fm.mediasoup.callback.CallbackSdp;
import com.fm.mediasoup.Transport;
import com.fm.mediasoup.json.RtpParameters;
import com.fm.mediasoup.message.SdpMessage;
import com.fm.mediasoup.sdp.Candidate;
import com.fm.mediasoup.sdp.Fingerprint;
import com.fm.mediasoup.sdp.Media;
import com.fm.webrtc.message.MediaStreamMessage;
import com.fm.webrtc.modle.StreamModel;
import com.fm.webrtc.websocket.message.callback.CallConnectMessage;
import com.fm.webrtc.websocket.message.callback.CallReceiveMessage;
import com.fm.webrtc.websocket.message.MessageType;
import com.fm.webrtc.websocket.message.MessageUtil;
import com.fm.webrtc.websocket.message.transport.DtlsParameters;
import com.fm.webrtc.websocket.message.transport.Fingerprints;
import com.fm.webrtc.websocket.message.transport.IceCandidates;
import com.fm.webrtc.websocket.message.request.AuthorizationResponse;
import com.fm.webrtc.websocket.message.request.ConnectWebrtcTransport;
import com.fm.webrtc.websocket.message.request.CreateTransport;
import com.fm.webrtc.websocket.message.request.PauseProduce;
import com.fm.webrtc.websocket.message.request.Ping;
import com.fm.webrtc.websocket.message.request.Produce;
import com.fm.webrtc.websocket.message.request.ResumeProduce;
import com.fm.webrtc.websocket.message.response.ConnectWebrtcTransportResponse;
import com.fm.webrtc.websocket.message.response.CreateTransportResponse;
import com.fm.webrtc.websocket.message.response.JoinResponse;
import com.fm.webrtc.websocket.message.response.MemberDestroyResponse;
import com.fm.webrtc.websocket.message.response.NewConsumeResponse;
import com.fm.webrtc.websocket.message.response.PauseConsumeResponse;
import com.fm.webrtc.websocket.message.response.PongResponse;
import com.fm.webrtc.websocket.message.response.ProduceResponse;
import com.fm.webrtc.websocket.message.response.ResumeConsumeResponse;
import com.fm.webrtc.websocket.message.response.StatisticsResponse;
import com.google.gson.Gson;

import org.webrtc.EglBase;
import org.webrtc.MediaStream;
import org.webrtc.SurfaceTextureHelper;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 接收视频 流程
 * 1、 join
 * 2、 createTransport
 * 3、 newConsume
 * 4、 setOfferDescription
 * 5、 webrtc -> createAnswer
 * 6、 connectTransport
 */


/**
 * 发送视频 流程
 * 1、 join
 * 2、 createTransport
 * 3、 webrtc -> createOffer
 * 4、 setAnswerDescription
 * 5、 connectTransport
 * 6、 produce
 */

/**
 * 数据请求控制器
 */
public class RequestController implements CallReceiveMessage, CallbackMediaStream, CallbackSdp {

    private final static String TAG = RequestController.class.getSimpleName();

    /**
     * websocket 消息 处理
     */
    private MessageController messageController;

    private JoinResponse joinResponse;

    private CreateTransportResponse createTransportResponse;

    private NewConsumeResponse newConsumeResponse;

    private MemberDestroyResponse memberDestroyResponse;

    private MediaStreamMessage mediaStreamMessage;

    private ScheduledThreadPoolExecutor pingScheduler;

    private ScheduledThreadPoolExecutor pongScheduler;

    private int pongHeartbeat = 12000;

    private int pingHeartbeat = 8000;

    public void setCallConnect(CallConnectMessage callConnect) {
        this.callConnect = callConnect;
    }

    private CallConnectMessage callConnect;

    private Context context;

    private Sdp sendSdp;

    private int width = 480;
    private int height = 320;
    private int fps = 30;

    private Sdp offerSdp;


    private Producer producer;

    /**
     * 渲染上下文
     */
    private EglBase.Context eglBaseContext;

    /**
     * 存放消费的 数据流信息
     */
    private HashMap<String, StreamModel> streamModelMap = new HashMap<>();


    /**
     * 接收  transport
     */
    private Transport receiveTransport;

    private Transport sendTransport;



    private SurfaceTextureHelper surfaceTextureHelper;

    /**
     *
     * @param context
     * @param eglBaseContext
     * @param mediaStreamMessage 流数据回调
     */
    public RequestController(Context context, EglBase.Context eglBaseContext, MediaStreamMessage mediaStreamMessage, CallConnectMessage callConnect, SurfaceTextureHelper surfaceTextureHelper){
        this.context = context;
        this.eglBaseContext = eglBaseContext;
        this.callConnect = callConnect;
        messageController = new MessageController(this);
        this.mediaStreamMessage = mediaStreamMessage;
        this.surfaceTextureHelper = surfaceTextureHelper;
    }


    /**
     * 重新发起请求
     */
    private void resumeRequest(){
        if (sendTransport != null) sendTransport.release();
        if (receiveTransport != null) receiveTransport.release();
        if (pingScheduler != null) pingScheduler.shutdown();
        if (pongScheduler != null) pongScheduler.shutdown();
        heartbeat();
        messageController = new MessageController(this);
    }

    /**
     * 设置流信息
     * @param streamModel
     * @param consumeId
     * @param streamId
     * @param kind
     * @param pause
     */
    private void setStreamModelData(StreamModel streamModel, String consumeId, String streamId, String kind, boolean pause){
        if (kind.equals("video")) {
            streamModel.setVid(consumeId);
            streamModel.setVStreamId(streamId);
        }
        if (kind.equals("audio")) {
            streamModel.setAid(consumeId);
            streamModel.setAStreamId(streamId);
        }
    }

    /**
     * 添加 流信息
     * @param peerId
     * @param consumeId
     * @param streamId
     * @param kind
     * @param pause
     */
    private void addStreamModel(String peerId, String consumeId, String streamId, String kind, boolean pause){
        if (streamModelMap.containsKey(peerId)) {
            setStreamModelData(streamModelMap.get(peerId), consumeId, streamId, kind, pause);
        } else {
            StreamModel streamModel = new StreamModel(peerId);
            setStreamModelData(streamModel, consumeId, streamId, kind, pause);
            streamModelMap.put(peerId, streamModel);
        }
    }

    /**
     * 创建 tranpsort
     * @param isConsumer true 发送 false 接收
     * @return
     */
    public CreateTransport createTransport(boolean isConsumer){
        CreateTransport createTransport =  new  CreateTransport();
        createTransport.isConsumer = isConsumer;
        this.messageController.send(MessageType.createTransport, createTransport);
        return createTransport;
    }

    /**
     * 实例化 transport
     * @param createTransportResponse
     */
    public void initTransport(CreateTransportResponse createTransportResponse){
        List<Fingerprint> fingerprintList = new ArrayList<>();
        List<Candidate> candidateList = new ArrayList<>();
        for (Fingerprints fingerprints: createTransportResponse.dtlsParameters.fingerprints){
            fingerprintList.add(fingerprints.conversionSdpFingerPrint());
        }
        for (IceCandidates iceCandidates: createTransportResponse.iceCandidates) {
            candidateList.add(iceCandidates.conversionSdpCandidate());
        }
        if (createTransportResponse.isConsumer){
            this.receiveTransport = new Transport(createTransportResponse.id, this.context, this.eglBaseContext, fingerprintList, candidateList, createTransportResponse.iceParameters.conversionSdpIceParameter());
            this.receiveTransport.setCallbackMediaStream(this);
            this.receiveTransport.setCallbackSdp(this);
        } else {
            this.sendTransport = new Transport(createTransportResponse.id, this.context, this.eglBaseContext, fingerprintList, candidateList, createTransportResponse.iceParameters.conversionSdpIceParameter());
            this.sendTransport.setCallbackSdp(this);
            this.callConnect.producer();
        }
    }



    /**
     * 创建消费者
     * @param newConsumeResponse
     */
    public void createConsumer(NewConsumeResponse newConsumeResponse){
        if (this.receiveTransport != null) {
            UUID uuid = UUID.randomUUID();
            String streamId = uuid.toString();
            newConsumeResponse.rtpParameters.streamId = streamId;
            addStreamModel(newConsumeResponse.peerId, newConsumeResponse.id, streamId, newConsumeResponse.kind, newConsumeResponse.pause);
            newConsumeResponse.rtpParameters.kind = newConsumeResponse.kind;
            this.receiveTransport.consumer(newConsumeResponse.id, newConsumeResponse.rtpParameters);
        }
    }


    /**
     * 创建 生产者
     * @param video
     * @param audio
     * @param trackCallback
     */
    public Producer createProducer(boolean video, boolean audio, TrackCallback trackCallback){
        producer = this.sendTransport.producer(video, audio, trackCallback, this.surfaceTextureHelper);
       return producer;
    }

    /**
     * 发送sdp 信息 给服务端， 开始发送 媒体数据
     * @param localSdp  本地的sdp 信息
     * @param answerSdp  远端sdp 信息
     */
    public void sendProducer(Sdp localSdp, Sdp answerSdp){
        for (Media answerMedia: answerSdp.media) {
            RtpParameters.Builder builder = new RtpParameters.Builder().
                    setCodecs(answerMedia.rtp, answerMedia.rtcpFb, answerMedia.type).
                    setKind(answerMedia.type).
                    setMid(answerMedia.mid).
                    setHeaderExtensions(answerMedia.ext);
            for (Media localMedia: localSdp.media) {
                if (localMedia.type.equals(answerMedia.type)) {
                    builder.setRtcp(localMedia.ssrcs).setEncodings(localMedia.ssrcs);
                }
            }
            Produce produce = new Produce.Builder().setKind(answerMedia.type).setRtpParameters(builder.build()).setTransportId(this.sendTransport.getId()).build();
            this.messageController.send(MessageType.producer, produce);
        }

    }


    /**
     * 连接 transport
     * @param fingerprintList
     * @param transportId
     * @param consume
     */
    private void connectTransport(List<Fingerprint> fingerprintList, String transportId, boolean consume){
       DtlsParameters dtlsParameters = new DtlsParameters.Builder().setRole("client").setFingerprints(fingerprintList).build();
       ConnectWebrtcTransport connectWebrtcTransport = new ConnectWebrtcTransport.Builder().
               setDtlsParameters(dtlsParameters).
               setConsume(consume).
               setTransportId(transportId).build();
       Log.e(TAG, connectWebrtcTransport.toString());
       this.messageController.send(MessageType.connectWebRtcTransport, connectWebrtcTransport);
    }


    @Override
    public void authorizationResponse(AuthorizationResponse authorizationResponse) {
        this.messageController.join();
    }

    @Override
    public void joinResponse(JoinResponse joinResponse) {
        this.joinResponse = joinResponse;
        if (this.callConnect != null) this.callConnect.connectSuccess();
        ping();
    }

    @Override
    public void createTransportResponse(CreateTransportResponse createTransportResponse) {
        initTransport(createTransportResponse);
    }

    @Override
    public void newConsumeResponse(NewConsumeResponse newConsumeResponse) {
        createConsumer(newConsumeResponse);
    }

    @Override
    public void memberDestroyResponse(MemberDestroyResponse memberDestroyResponse) {
        this.streamModelMap.remove(memberDestroyResponse.peerId);
        this.mediaStreamMessage.callStream(this.streamModelMap);
    }

    @Override
    public void pauseConsumeResponse(PauseConsumeResponse pauseConsumeResponse) {
        updateStreamModelStatus(true, pauseConsumeResponse.peerId, pauseConsumeResponse.consumerId);
    }

    @Override
    public void resumeConsumeResponse(ResumeConsumeResponse resumeConsumeResponse) {
        Log.e(TAG, resumeConsumeResponse.toString());
        updateStreamModelStatus(false, resumeConsumeResponse.peerId, resumeConsumeResponse.consumerId);
    }

    @Override
    public void connectWebrtcTransportResponse(ConnectWebrtcTransportResponse connectWebrtcTransportResponse) {
        if (!connectWebrtcTransportResponse.isConsumer) {
            sendProducer(offerSdp, sendSdp);
        }
    }

    @Override
    public void produce(ProduceResponse produceResponse) {
        if (produceResponse.kind.equals("video")){
            this.producer.setVProducerId(produceResponse.producerId);
        } else if (produceResponse.kind.equals("audio")) {
            this.producer.setAProducerId(produceResponse.producerId);
        }
    }

    /**
     * 心跳检测
     */
    private void heartbeat(){
        if (pongScheduler != null) pongScheduler.shutdown();
        pongScheduler = new ScheduledThreadPoolExecutor(1);
        Runnable task = new Runnable() {
            @Override
            public void run() {
                // 在这里执行定时任务的代码
                Log.e(TAG, "=============超时===============");
                messageController.close();
                resumeRequest();
            }
        };

// 在1000毫秒后开始执行，然后每2000毫秒执行一次
        pongScheduler.scheduleAtFixedRate(task, pongHeartbeat, pongHeartbeat, TimeUnit.MILLISECONDS);
    }

    private void ping(){
        if (pingScheduler != null) pingScheduler.shutdown();
        pingScheduler = new ScheduledThreadPoolExecutor(1);
        Runnable task = new Runnable() {
            @Override
            public void run() {
                // 在这里执行定时任务的代码
                messageController.send(MessageType.ping, new Ping());
            }
        };
        pingScheduler.scheduleAtFixedRate(task, pingHeartbeat, pingHeartbeat, TimeUnit.MILLISECONDS);
    }

    @Override
    public void pong(PongResponse pongResponse) {
        heartbeat();
    }

    @Override
    public void statistics(StatisticsResponse statisticsResponse) {
        if (statisticsResponse.availableOutgoingBitrate < statisticsResponse.rtpSendBitrate){
            Log.e(TAG, "带宽不足");
            this.resetVideo();
        }
    }

    /**
     * 修改 数据流状态
     * @param status
     * @param peerId
     * @param consumerId
     */
    private void updateStreamModelStatus(boolean status, String peerId, String consumerId){
        StreamModel streamModel = this.streamModelMap.get(peerId);
        if (streamModel!= null) {
            if (streamModel.getVid() != null && streamModel.getVid().equals(consumerId)) {
                streamModel.setVPause(status);
            }
            if (streamModel.getAid() != null && streamModel.getAid().equals(consumerId)) {
                streamModel.setAPause(status);
            }
        }
        this.mediaStreamMessage.callStream(this.streamModelMap);
    }



    @Override
    public void addStream(MediaStream mediaStream) {
        String id = mediaStream.getId();
        Log.e(TAG, "streamId == "+id);
        for (Map.Entry<String, StreamModel> streamModelEntry: streamModelMap.entrySet()) {
            String vStreamId = streamModelEntry.getValue().getVStreamId();
            String aStreamId = streamModelEntry.getValue().getAStreamId();
            if (vStreamId != null && vStreamId.equals(id)) {
                streamModelEntry.getValue().setVideoTrack(mediaStream.videoTracks.get(0));
                streamModelEntry.getValue().setVPause(false);
            }
            if (aStreamId != null && aStreamId.equals(id)) {
                streamModelEntry.getValue().setAudioTrack(mediaStream.audioTracks.get(0));
                streamModelEntry.getValue().setAPause(false);
            }
        }
        this.mediaStreamMessage.callStream(this.streamModelMap);
    }

    @Override
    public void removeStream(String id) {

    }

    @Override
    public void removeStream(MediaStream mediaStream) {

    }


    @Override
    public void offer(SdpMessage sdpMessage) { //发送数据
        Log.e(TAG, sdpMessage.getSdp());
        String jsonSdp = SdpTransform.parse(sdpMessage.getSdp());
        Gson gson = new Gson();
        offerSdp = gson.fromJson(jsonSdp, Sdp.class);
        this.sendSdp = this.sendTransport.createAnswerSdp(offerSdp, joinResponse.rtpCapabilities.headerExtensions);
        connectTransport(offerSdp.getFingerprintList(), this.sendTransport.getId(), false);
        this.sendTransport.setAnswerDescription(SdpTransform.write(MessageUtil.toJson(this.sendSdp)));
    }

    @Override
    public void answer(SdpMessage sdpMessage) { //接收数据
        Log.e(TAG, sdpMessage.getSdp());
        String jsonSdp = SdpTransform.parse(sdpMessage.getSdp());
        Gson gson = new Gson();
        Sdp answerSdp = gson.fromJson(jsonSdp, Sdp.class);
        if (!this.receiveTransport.isConnect()) {
            connectTransport(answerSdp.getFingerprintList(), this.receiveTransport.getId(), true);
            this.receiveTransport.setConnect(true);
        }
    }

    public void pauseVideo(){
        if (this.producer != null) {
           String vid =  this.producer.getVProducerId();
           this.producer.pauseVideo();
           this.messageController.send(MessageType.pauseProducer, new PauseProduce.Builder().setProducerId(vid).build());
        }
    }

    public void resumeVideo(){
        if (this.producer != null) {
            String vid = this.producer.getVProducerId();
            this.producer.resumeVideo();
            this.messageController.send(MessageType.resumeProducer, new ResumeProduce.Builder().setProducerId(vid).build());
        }
    }

    public void resetVideo(){
        if (this.producer != null) {
            if (this.width > 40) {
                this.width /= 2;
                this.height /= 2;
                Log.e(TAG, "width=" + width);
                this.producer.resetVideo(width, height, fps);
            }
        }
    }

    public void pauseAudio(){
        if (this.producer != null) {
            String aid =  this.producer.getAProducerId();
            this.producer.pauseAudio();
            this.messageController.send(MessageType.pauseProducer, new PauseProduce.Builder().setProducerId(aid).build());
        }
    }

    public void resumeAudio(){
        if (this.producer != null) {
            String aid = this.producer.getAProducerId();
            this.producer.resumeAudio();
            this.messageController.send(MessageType.resumeProducer, new ResumeProduce.Builder().setProducerId(aid).build());
        }
    }

    /**
     * 释放
     */
    public void release(){
        if (pingScheduler != null) {
            pingScheduler.shutdown();
        }
        if (pongScheduler != null) {
            pongScheduler.shutdown();
        }
        if (sendTransport != null) sendTransport.release();
        if (receiveTransport != null) receiveTransport.release();
        messageController.close();
    }
}
